/**
 * Copyright 2008 Mathias Doenitz, http://lis.to/
 *
 * This file is part of the lis.to java desktop client. The lis.to java desktop client is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * The lis.to java desktop client is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with the lis.to java desktop client.
 * If not, see http://www.gnu.org/licenses/
 */

package listo.client;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.melloware.jintellitype.HotkeyListener;
import listo.client.actions.ActionManager;
import listo.client.actions.Actions;
import listo.client.taskfilters.SearchFilter;
import listo.client.tasklists.MainFormTaskList;
import listo.client.tasklists.TaskList;
import listo.client.tasklists.TaskListManager;
import listo.client.viewlists.*;
import listo.utils.logging.Log;
import listo.utils.swing.DocumentAdapter;
import org.apache.commons.lang.StringUtils;
import org.simplericity.macify.eawt.ApplicationAdapter;
import org.simplericity.macify.eawt.ApplicationEvent;
import org.simplericity.macify.eawt.DefaultApplication;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.*;

@Singleton
public class MainForm extends JFrame {

    @Inject private Log log;
    @Inject private Lang lang;
    @Inject private Application application;
    @Inject private AppConfig appConfig;
    @Inject private WindowState windowState;
    @Inject private Preferences preferences;
    @Inject private Actions actions;
    @Inject private AllTasksViewList allTasksViewList;
    @Inject private FolderViewList folderViewList;
    @Inject private TrashViewList trashViewList;
    @Inject private Provider<MainFormTaskList> mainFormTaskListProvider;
    @Inject private StatusBarManager statusBarManager;
    @Inject private ViewListManager viewListManager;
    @Inject private TaskListManager taskListManager;
    @Inject private ImageProvider imageProvider;
    @Inject private GlobalHotKeySupport globalHotKeySupport;
    @Inject private SearchFilter searchFilter;
    @Inject private MenuBuilder menuBuilder;

    @SuppressWarnings({"UnusedDeclaration"})
    @Inject private ActionManager actionManager;
    @SuppressWarnings({"UnusedDeclaration"})
    @Inject private ReminderRunner reminderRunner;
    @SuppressWarnings({"UnusedDeclaration"})
    @Inject private CleanupRunner cleanupRunner;

    private final KeyboardHandler keyboardHandler = new KeyboardHandler();

    private JSplitPane mainSplitPane;
    private JTabbedPane tabPane;
    private TrayIcon trayIcon;
    private MenuItem trayShowHideWindowItem;
    private JTextField searchTextField;
    private JLabel clearSearchLabel;
    private JMenuBar menuBar;

    private final org.simplericity.macify.eawt.Application macifyApplication;
    private boolean alreadyDisposed;

    // org.simplericity.macify requires this to be a public class
    public class MacApplicationHandler extends ApplicationAdapter {

        public void handleAbout(ApplicationEvent event) {
            actions.aboutAction.actionPerformed(null);
            event.setHandled(true);
        }

        public void handlePreferences(ApplicationEvent event) {
            actions.preferencesAction.actionPerformed(null);
            event.setHandled(true);
        }

        public void handleQuit(ApplicationEvent event) {
            actions.exitAction.actionPerformed(null);
        }
    }

    public MainForm() {
        macifyApplication = new DefaultApplication();
        if (macifyApplication.isMac()) {
            macifyApplication.addPreferencesMenuItem();
            macifyApplication.setEnabledPreferencesMenu(true);
            macifyApplication.addApplicationListener(new MacApplicationHandler());
        }
    }

    public void run() {
        createTrayIcon();
        createUI();

        log.debug("Installing UI event wiring");

        allTasksViewList.addKeyListener(keyboardHandler);
        folderViewList.addKeyListener(keyboardHandler);
        trashViewList.addKeyListener(keyboardHandler);
        tabPane.addKeyListener(keyboardHandler);

        addWindowListener(new WindowAdapter() {
            // called when the user clicks the close icon in the window decoration
            public void windowClosing(WindowEvent e) {
                if (trayIcon == null) {
                    application.shutdown();
                } else {
                    hideWindow();
                }
            }
        });

        tabPane.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                taskListManager.setActiveTabIndex(tabPane.getSelectedIndex());
            }
        });

        if (globalHotKeySupport.isAvailable()) {
            globalHotKeySupport.addHotKeyListener(new HotkeyListener() {
                public void onHotKey(int identifier) {
                    log.debug("Global hotkey activated, hotkey id %1$s", identifier);
                    switch (identifier) {
                        case GlobalHotKeySupport.HOTKEY_OPEN_WINDOW:
                            if (isActive()) {
                                if (trayIcon != null) hideWindow();
                            } else {
                                showWindow();
                            }
                            break;
                        case GlobalHotKeySupport.HOTKEY_NEW_TASK:
                            SwingUtilities.invokeLater(new Runnable() {
                                public void run() {
                                    actions.addTaskAction.actionPerformed(null);
                                }
                            });
                            break;
                    }
                }
            });
        }

        if (trayIcon != null) {
            getRootPane().registerKeyboardAction(new ActionListener() {
                public void actionPerformed(ActionEvent e) { hideWindow(); }
            }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        }

        searchTextField.getDocument().addDocumentListener(new DocumentAdapter() {
            public void textChanged() {
                String text = searchTextField.getText();
                clearSearchLabel.setEnabled(StringUtils.isNotEmpty(text));
                searchFilter.setSearchText(text);
            }
        });

        searchTextField.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    (taskListManager.getActiveTaskList().isEmpty() ? viewListManager.getLastListSelected() :
                            taskListManager.getActiveTaskList()).requestFocusInWindow();
                }
            }
        });

        clearSearchLabel.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                searchTextField.setText("");
            }
        });

        actions.loadAction.actionPerformed(null);
        Action action;
        switch (windowState.getCompletionView()) {
            case all:
                action = actions.viewAllCompletedAction;
                break;
            case none:
                action = actions.viewNoCompletedAction;
                break;
            case todays:
                action = actions.viewTodaysCompletedAction;
                break;
            default:
                throw new IllegalStateException();

        }
        menuBuilder.findMenuItem(menuBar, action).doClick();
        MainForm.this.validate();
        taskListManager.setActiveTabIndex(tabPane.getSelectedIndex());
    }

    private void createUI() {
        log.debug("Creating mainform UI");

        JPanel panel;
        JScrollPane scrollPane;
        JLabel label;

        // frame
        this.setMinimumSize(new Dimension(appConfig.getWindowMinSizeX(), appConfig.getWindowMinSizeY()));
        this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        this.setTitle("lis.to - done!");
        this.setIconImage(imageProvider.getImage("16x16/logo.png"));

        // main panel
        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout(0, 0));
        this.setContentPane(mainPanel);

        // toolbar
        JToolBar toolBar = new JToolBar();
        toolBar.add(actions.undoAction).addMouseListener(statusBarManager);
        toolBar.add(actions.redoAction).addMouseListener(statusBarManager);
        toolBar.addSeparator();
        toolBar.add(actions.addFolderAction).addMouseListener(statusBarManager);
        toolBar.add(actions.editFolderAction).addMouseListener(statusBarManager);
        toolBar.add(actions.deleteFoldersAction).addMouseListener(statusBarManager);
        toolBar.addSeparator();
        toolBar.add(actions.addTaskAction).addMouseListener(statusBarManager);
        toolBar.add(actions.toggleTasksCompletedAction).addMouseListener(statusBarManager);
        toolBar.add(actions.editTaskAction).addMouseListener(statusBarManager);
        toolBar.add(actions.deleteTasksAction).addMouseListener(statusBarManager);
        toolBar.add(actions.detachCurrentFoldersAction).addMouseListener(statusBarManager);
        toolBar.addSeparator();

        panel = new JPanel();
        searchTextField = new JTextField();
        Dimension size = searchTextField.getPreferredSize();
        size.width = 150;
        searchTextField.setPreferredSize(size);
        label = new JLabel(imageProvider.getIcon("16x16/search.png"));
        label.setLabelFor(searchTextField);
        panel.add(label);
        panel.add(searchTextField);
        clearSearchLabel = new JLabel(imageProvider.getIcon("16x16/cancel.png"));
        clearSearchLabel.setEnabled(false);
        panel.add(clearSearchLabel);
        panel.setMinimumSize(panel.getPreferredSize());
        panel.setMaximumSize(panel.getPreferredSize());
        toolBar.add(panel);

        mainPanel.add(toolBar, BorderLayout.NORTH);

        // statusbar
        JPanel statusBar = new JPanel();
        statusBar.setLayout(new BorderLayout(0, 0));
        statusBar.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
        label = new JLabel();
        statusBarManager.setMessageLabel(label);
        statusBar.add(label, BorderLayout.WEST);
        label = new JLabel();
        statusBarManager.setStatusLabel(label);
        statusBar.add(label, BorderLayout.EAST);
        mainPanel.add(statusBar, BorderLayout.SOUTH);

        // main split pane
        UIManager.put("SplitPaneDivider.border", BorderFactory.createEmptyBorder());
        mainSplitPane = new JSplitPane();
        mainSplitPane.setDividerSize(3);
        mainSplitPane.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));
        mainPanel.add(mainSplitPane, BorderLayout.CENTER);

        // view lists
        JPanel viewPanel = new JPanel();
        viewPanel.setLayout(new BorderLayout(0, 0));
        viewPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
        viewPanel.add(allTasksViewList, BorderLayout.NORTH);

        panel = new JPanel();
        panel.setLayout(new BorderLayout(0, 0));
        panel.setBorder(BorderFactory.createEmptyBorder(10, 0, 5, 0));
        scrollPane = new JScrollPane();
        scrollPane.setBorder(BorderFactory.createEmptyBorder());
        scrollPane.setViewportView(folderViewList);
        panel.add(scrollPane, BorderLayout.CENTER);
        viewPanel.add(panel, BorderLayout.CENTER);

        viewPanel.add(trashViewList, BorderLayout.SOUTH);
        mainSplitPane.setLeftComponent(viewPanel);

        // task tabs and lists
        tabPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
        mainSplitPane.setRightComponent(tabPane);

        tabPane.addTab("All", getTaskListScrollPane());
        tabPane.addTab("Today", getTaskListScrollPane());
        tabPane.addTab("Tomorrow", getTaskListScrollPane());
        tabPane.addTab("Next 7 days", getTaskListScrollPane());

        // menu
        menuBar = new JMenuBar();
        menuBar.add(menuBuilder.createMenu(lang.get("menu.file"), 'f',
                macifyApplication.isMac() ? new Action[] {actions.clearAllAction} : new Action[] {
                        actions.clearAllAction,
                        actions.preferencesAction,
                        null,
                        actions.exitAction}));
        menuBar.add(menuBuilder.createMenu(lang.get("menu.edit"), 'e',
                actions.undoAction,
                actions.redoAction,
                actions.findAction,
                null,
                actions.emptyTrashAction));
        menuBar.add(menuBuilder.createMenu(lang.get("menu.view"), 'v',
                actions.viewAllCompletedAction,
                actions.viewTodaysCompletedAction,
                actions.viewNoCompletedAction));
        menuBar.add(menuBuilder.createMenu(lang.get("menu.folder"), 'o',
                actions.addFolderAction,
                actions.editFolderAction,
                actions.deleteFoldersAction));
        menuBar.add(menuBuilder.createMenu(lang.get("menu.task"), 't',
                actions.addTaskAction,
                actions.editTaskAction,
                actions.detachCurrentFoldersAction,
                null,
                actions.toggleTasksCompletedAction));
        if (!macifyApplication.isMac()) {
            menuBar.add(menuBuilder.createMenu(lang.get("menu.help"), 'h', actions.aboutAction));
        }

        this.setJMenuBar(menuBar);

        this.setFocusTraversalPolicy(new CustomTraversalPolicy());

        // set saved size and split position
        this.setLocation(windowState.getWindowPosX(), windowState.getWindowPosY());
        this.setSize(windowState.getWindowSizeX(), windowState.getWindowSizeY());
        mainSplitPane.setDividerLocation(windowState.getWindowSplitX());

        if (preferences.getStartMinimized() && trayIcon == null){
            log.warn("No TrayIcon, cannot start minimized");
            preferences.setStartMinimized(false);
        }

        if (preferences.getStartMinimized()) {
            log.info("Starting minimized");
            SplashScreen splashScreen = SplashScreen.getSplashScreen();
            if (splashScreen != null) {
                splashScreen.close(); // we need to manually close, since we are not showing a window
            }
        } else {
            log.info("Displaying mainform");
            showWindow();
        }

        application.addShutdownListener(new Application.ShutdownListener() {
            public void onShutdown() {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        log.debug("Received shutdown event");
                        close();
                    }
                });
            }
        });
    }

    private JScrollPane getTaskListScrollPane() {
        TaskList taskList = mainFormTaskListProvider.get();
        taskList.addKeyListener(keyboardHandler);
        JScrollPane scrollPane = new JScrollPane(taskList);
        scrollPane.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
        scrollPane.setBackground(taskList.getBackground());
        return scrollPane;
    }

    private void saveWindowState() {
        windowState.setWindowPosX(this.getX());
        windowState.setWindowPosY(this.getY());
        windowState.setWindowSizeX(this.getWidth());
        windowState.setWindowSizeY(this.getHeight());
        windowState.setWindowSplitX(mainSplitPane.getDividerLocation());
        if (menuBuilder.findMenuItem(menuBar, actions.viewAllCompletedAction).isSelected()) {
            windowState.setCompletionView(WindowState.CompletionView.all);
        } else if (menuBuilder.findMenuItem(menuBar, actions.viewTodaysCompletedAction).isSelected()) {
            windowState.setCompletionView(WindowState.CompletionView.todays);
        } else {
            windowState.setCompletionView(WindowState.CompletionView.none);
        }
        windowState.save();
    }

    private void close() {
        if (isVisible()) saveWindowState();

        if (trayIcon != null) {
            log.debug("Removing system tray icon");
            SystemTray.getSystemTray().remove(trayIcon);
            trayIcon = null;
        }
        if (!alreadyDisposed) {
            log.debug("Disposing MainForm");
            dispose();
            alreadyDisposed = true;
        }
    }

    private void hideWindow() {
        if (isVisible()) {
            saveWindowState();
            setVisible(false);
            if (trayShowHideWindowItem != null) trayShowHideWindowItem.setLabel("Show Window");
        }
    }

    private void showWindow() {
        if (trayShowHideWindowItem != null) trayShowHideWindowItem.setLabel("Hide Window");
        setVisible(true);
        toFront();
    }

    private void showHideWindow() {
        if (isVisible()) {
            hideWindow();
        } else {
            showWindow();
        }
    }

    private void createTrayIcon() {
        if (!preferences.useTrayIcon()) {
            log.debug("Tray icon support disabled");
            return;
        } else {
            log.debug("Creating tray icon");
        }

        if (!SystemTray.isSupported()) {
            log.warn("SystemTray is not supported");
            return;
        }

        PopupMenu popupMenu = new PopupMenu();
        trayShowHideWindowItem = new MenuItem("Show Window");
        trayShowHideWindowItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showHideWindow();
            }
        });
        popupMenu.add(trayShowHideWindowItem);

        MenuItem trayNewTaskItem = new MenuItem("New Task");
        trayNewTaskItem.addActionListener(actions.addTaskAction);
        popupMenu.add(trayNewTaskItem);
        popupMenu.addSeparator();

        MenuItem exitItem = new MenuItem("Exit");
        popupMenu.add(exitItem);
        exitItem.addActionListener(actions.exitAction);

        // under windows we use the 16x16 icon since it is then already scaled down nicely to the
        // required size, we could also use the big 128x128 logo under windows but the downscaling
        // done by java produces a more jaggedy tray icon
        boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
        trayIcon = new TrayIcon(imageProvider.getImage(isWindows ? "16x16/logo.png" : "logo.png"));
        trayIcon.setImageAutoSize(true);
        trayIcon.setToolTip("lis.to");
        trayIcon.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    switch (preferences.getTrayIconDoubleClick()) {
                        case NewTask:
                            actions.addTaskAction.actionPerformed(null);
                            break;
                        case ShowHide:
                            showHideWindow();
                            break;
                    }
                }
            }
        });
        trayIcon.setPopupMenu(popupMenu);

        try {
            SystemTray.getSystemTray().add(trayIcon);
        }
        catch (AWTException e) {
            log.error("TrayIcon could not be added");
            trayIcon = null;
        }
    }

    public void focusSearchTextField() {
        searchTextField.requestFocusInWindow();
    }

    private class CustomTraversalPolicy extends FocusTraversalPolicy {

        public Component getComponentAfter(Container focusCycleRoot, Component aComponent) {
            if (aComponent instanceof ViewList) return tabPane;
            if (aComponent == tabPane) return taskListManager.getActiveTaskList();
            return viewListManager.getLastListSelected();
        }

        public Component getComponentBefore(Container focusCycleRoot, Component aComponent) {
            if (aComponent instanceof ViewList) return taskListManager.getActiveTaskList();
            if (aComponent instanceof TaskList) return tabPane;
            return viewListManager.getLastListSelected();
        }

        public Component getDefaultComponent(Container focusCycleRoot) {
            return allTasksViewList;
        }

        public Component getLastComponent(Container focusCycleRoot) {
            return taskListManager.getActiveTaskList();
        }

        public Component getFirstComponent(Container focusCycleRoot) {
            return allTasksViewList;
        }
    }

    private class KeyboardHandler extends KeyAdapter {
        public void keyPressed(KeyEvent e) {
            if (e.isConsumed() || e.isAltDown()) return;
            JComponent component = (JComponent) e.getSource();

            if (component instanceof ViewList) {
                if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                    TaskList taskList = taskListManager.getActiveTaskList();
                    (taskList.isEmpty() ? tabPane : taskList).requestFocusInWindow();
                    e.consume();
                }
            } else if (component instanceof TaskList) {
                if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                    if (e.isControlDown() && tabPane.getSelectedIndex() > 0) {
                        tabPane.setSelectedIndex(tabPane.getSelectedIndex() - 1);
                        if (!taskListManager.getActiveTaskList().isEmpty()) {
                            taskListManager.getActiveTaskList().requestFocusInWindow();
                        } else {
                            tabPane.requestFocusInWindow();
                        }
                    } else {
                        viewListManager.getLastListSelected().requestFocusInWindow();
                    }
                    e.consume();
                } else if (e.getKeyCode() == KeyEvent.VK_RIGHT && e.isControlDown()) {
                    if (tabPane.getSelectedIndex() < tabPane.getTabCount() - 1) {
                        tabPane.setSelectedIndex(tabPane.getSelectedIndex() + 1);
                    }
                    if (!taskListManager.getActiveTaskList().isEmpty()) {
                        taskListManager.getActiveTaskList().requestFocusInWindow();
                    } else {
                        tabPane.requestFocusInWindow();
                    }
                    e.consume();
                } else if (e.getKeyCode() == KeyEvent.VK_UP && ((TaskList) component).getSelectedIndex() == 0) {
                    tabPane.requestFocusInWindow();
                    e.consume();
                }
            } else if (component == tabPane) {
                if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                    if (tabPane.getSelectedIndex() == 0) {
                        viewListManager.getLastListSelected().requestFocusInWindow();
                    } else {
                        tabPane.setSelectedIndex(tabPane.getSelectedIndex() - 1);
                    }
                    e.consume();
                } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                    if (tabPane.getSelectedIndex() < tabPane.getTabCount() - 1) {
                        tabPane.setSelectedIndex(tabPane.getSelectedIndex() + 1);
                    }
                    e.consume();
                } else if (e.getKeyCode() == KeyEvent.VK_DOWN && taskListManager.getActiveTaskList() != null &&
                        !taskListManager.getActiveTaskList().isEmpty()) {
                    taskListManager.getActiveTaskList().requestFocusInWindow();
                    e.consume();
                }
            }
        }
    }

}
